package cc.creativecomputing.resonate;

import cc.creativecomputing.CCApp;
import cc.creativecomputing.CCApplicationManager;
import cc.creativecomputing.control.CCControl;
import cc.creativecomputing.graphics.CCCamera;
import cc.creativecomputing.graphics.CCDrawMode;
import cc.creativecomputing.graphics.CCGraphics.CCBlendMode;
import cc.creativecomputing.graphics.CCVBOMesh;
import cc.creativecomputing.graphics.shader.CCGLSLShader;
import cc.creativecomputing.graphics.shader.CCShaderBuffer;
import cc.creativecomputing.graphics.texture.CCTexture2D;
import cc.creativecomputing.graphics.texture.CCTextureIO;
import cc.creativecomputing.graphics.texture.CCTexture.CCTextureTarget;
import cc.creativecomputing.graphics.util.CCFrustum;
import cc.creativecomputing.io.CCIOUtil;
import cc.creativecomputing.math.CCMath;
import cc.creativecomputing.math.CCVector2f;
import cc.creativecomputing.math.CCVector3f;
import cc.creativecomputing.math.util.CCArcball;

public class ParticleAppCluster_03 extends CCApp{
	
	
	private CCShaderBuffer _myDataBuffer;
	private CCShaderBuffer _myLastDataBuffer;
	private CCShaderBuffer _myEmitClusterData;
	private CCShaderBuffer _myForceClusterData;
	
	private CCGLSLShader   _myEmitShader;
	private CCGLSLShader   _myVelocityShader;
	private CCGLSLShader   _myThruShader;
	private CCGLSLShader   _myUpdateShader;
	private CCGLSLShader   _myInitValueShader;
	
	private CCGLSLShader   _myDisplayShader;
	
	private CCVBOMesh _myMesh;
	
	private int particleW = 1000;
	private int particleH = 1000;
	
	private CCCamera _myCamera;
	
	private int nEmitCenters = 50;
	private int nAttractors  = 10;
	
	

	@Override
	public void setup() {
		
		_myCamera = new CCCamera(g);
		addControls("camera", "camera", this);
		
		_myEmitShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader03/emit_sphere_multi_fp.glsl"));
		_myEmitShader.load();
		
		_myDisplayShader = new CCGLSLShader (CCIOUtil.classPath(this, "shader03/display_vp.glsl"), 
				  							 CCIOUtil.classPath(this, "shader03/display_fp.glsl"));
		_myDisplayShader.load();
		
		_myVelocityShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader03/force_attractors_fp.glsl"));
		_myVelocityShader.load();
		
		_myThruShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader03/thru_fp.glsl"));
		_myThruShader.load();

		_myUpdateShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader03/update_fp.glsl"));
		_myUpdateShader.load();
		
		_myInitValueShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader03/init_fp.glsl"));
		_myInitValueShader.load();
		
		_myMesh = new CCVBOMesh(CCDrawMode.POINTS, particleW*particleH);
		for (int i=0; i<particleW; i++) {
			for (int j=0; j<particleH; j++) {
				_myMesh.addVertex((float)i/particleW, (float)j/particleH);
			}
		}
		
		_myDataBuffer = new CCShaderBuffer (32, 3, 4, particleW, particleH, CCTextureTarget.TEXTURE_2D);
		_myDataBuffer.clear();
		_myLastDataBuffer = new CCShaderBuffer (32, 3, 4, particleW, particleH, CCTextureTarget.TEXTURE_2D);
		_myLastDataBuffer.clear();
	
		_myEmitClusterData  = new CCShaderBuffer (32, 4, 1, nEmitCenters, 2, CCTextureTarget.TEXTURE_2D);
		_myForceClusterData = new CCShaderBuffer (32, 4, 1, nAttractors, 2, CCTextureTarget.TEXTURE_2D);
		
		g.pointSize(1);
		g.noBlend();
		
		_myInitValueShader.start();
		_myEmitClusterData.beginDraw();	
		g.beginShape(CCDrawMode.POINTS);
		
		float I = _myEmitClusterData.width();
		for (int i=0; i<I; i++) {
			g.textureCoords (CCMath.random(-400,400), CCMath.random(-200,200), CCMath.random(-200,-400));
			g.vertex (i+0.5f,0.5f);
			g.textureCoords (50f,(float)i/I,(float)(i+1)/I);
			g.vertex (i+0.5f,1.5f);
		}
		
		g.endShape();
		_myEmitClusterData.endDraw();
		_myInitValueShader.end();
		
		_myInitValueShader.start();
		_myForceClusterData.beginDraw();
		
		// repellers
		g.beginShape(CCDrawMode.POINTS);
		for (int i=0; i<nAttractors/2; i++) {
			//g.textureCoords (CCMath.random(-600,600), CCMath.random(-200,200), CCMath.random(-200,-400));
			g.textureCoords (-600f, 500f-i*200f, -400f);
			g.vertex(0.5f+i,0.5f);
			g.textureCoords (-1f,0f,0f);
			g.vertex(0.5f+i,1.5f);
		}
		
		// attractors
		for (int i=0; i<nAttractors/2; i++) {
			//g.textureCoords (CCMath.random(-600,600), CCMath.random(-200,200), CCMath.random(-200,-400));
			g.textureCoords (600f, 500f-i*200f, -400f);
			g.vertex(0.5f+i+nAttractors/2,0.5f);
			g.textureCoords (1f,0f,0f);
			g.vertex(0.5f+i+nAttractors/2,1.5f);
		}
		g.endShape();
		_myForceClusterData.endDraw();
		_myInitValueShader.end();
	}
	
	private void writeToAttractors () {
	
		
		_myInitValueShader.start();
		_myForceClusterData.beginDraw();
		g.beginShape(CCDrawMode.POINTS);
		g.textureCoords (mouseX, mouseY, 0);
		g.vertex (0.5f,0.5f);
		g.endShape();
		_myForceClusterData.endDraw();
		_myInitValueShader.end();
	}

	private void swap() {
		CCShaderBuffer tmp = _myDataBuffer;
		_myDataBuffer = _myLastDataBuffer;
		_myLastDataBuffer = tmp;
	}
	
	@Override
	public void update (float theDeltaTime) {
		
		writeToAttractors ();
		_myEmitShader.start();

		//g.texture (0, _myEmitTexture);
		g.texture (0, _myLastDataBuffer.attachment(0));
		g.texture (1, _myLastDataBuffer.attachment(1));
		g.texture (2, _myLastDataBuffer.attachment(2));
		g.texture (3, _myEmitClusterData.attachment(0));

		_myEmitShader.uniform1i("positions", 0);
		_myEmitShader.uniform1i("velocities", 1);
		_myEmitShader.uniform1i("infos", 2);
		_myEmitShader.uniform1i("clusterCenters", 3);
		
		_myEmitShader.uniform3f("randomSeed", CCMath.random(100f), CCMath.random(100f),  CCMath.random(3000,10000));
		_myEmitShader.uniform1i("dataW", _myEmitClusterData.width());
		_myEmitShader.uniform1i("dataH", _myEmitClusterData.height());
		
		_myDataBuffer.draw();
		_myEmitShader.end();
		g.noTexture();
		
		swap();
		
		
		_myThruShader.start();
		g.texture (0, _myLastDataBuffer.attachment(0));
		g.texture (1, _myLastDataBuffer.attachment(1));
		g.texture (2, _myLastDataBuffer.attachment(2));
		_myThruShader.uniform1i("positions", 0);
		_myThruShader.uniform1i("velocities", 1);
		_myThruShader.uniform1i("infos", 2);
		
		_myDataBuffer.draw();
		_myThruShader.end();
		g.noTexture();
		
		// todo add precalculated force texture here
		_myVelocityShader.start();
		g.texture (0, _myLastDataBuffer.attachment(0));
		g.texture (1, _myLastDataBuffer.attachment(1));
		g.texture (2, _myForceClusterData.attachment(0));
		
		_myVelocityShader.uniform1i("positions", 0);
		_myVelocityShader.uniform1i("velocities", 1);
		_myVelocityShader.uniform1i("attractors", 2);
		
		_myVelocityShader.uniform1i("dataW", _myForceClusterData.width());
		_myVelocityShader.uniform1i("dataH", _myForceClusterData.height());
		
		_myDataBuffer.draw(1);
		_myVelocityShader.end();
		g.noTexture();
		
		swap();
		
		_myUpdateShader.start();
		g.texture (0, _myLastDataBuffer.attachment(0));
		g.texture (1, _myLastDataBuffer.attachment(1));
		g.texture (2, _myLastDataBuffer.attachment(2));
		
		_myUpdateShader.uniform1i("positions", 0);
		_myUpdateShader.uniform1i("velocities", 1);
		_myUpdateShader.uniform1i("infos", 2);
		
		_myUpdateShader.uniform1f("deltaTime", theDeltaTime);
		
		
		_myDataBuffer.draw();
		_myUpdateShader.end();
		g.noTexture();
		
		swap();
		
		updateCam();
	}
	
	@Override
	public void draw() {
		
		g.pushAttribute();
		_myCamera.draw(g);
		g.clearColor(0.5f);
		g.clear();
		g.blend(CCBlendMode.ALPHA);
		
		_myDisplayShader.start();
		g.texture (0, _myLastDataBuffer.attachment(0));
		g.texture (1, _myLastDataBuffer.attachment(2));
		
		_myDisplayShader.uniform1i("positions", 0);
		_myDisplayShader.uniform1i("infos", 1);
		
		_myMesh.draw(g);
		_myDisplayShader.end();
		g.noTexture();
		g.popAttribute();
	}

	private void updateCam() {
		_myCamera.near(0.1f);
		_myCamera.far(4000f);
		
		_myCamera.position().x = 0;
		_myCamera.position().y = 0;
		_myCamera.position().z = 100;
			
		_myCamera.xRotation(0);
		_myCamera.yRotation(0);
		_myCamera.zRotation(0);
			
		_myCamera.fov(40);
	}
	public static void main (String[] args) {
		CCApplicationManager myManager = new CCApplicationManager(ParticleAppCluster_03.class);
		myManager.settings().size(1000, 1000);
		myManager.settings().display(0);
		myManager.settings().antialiasing(8);
		myManager.start();
	}
}